package com.only4play.portal.common.session;

import java.time.Instant;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 基于内存的sessionManager
 *
 * @author liyuncong
 * @version 1.0
 * @file StandardSessionManager
 * @brief StandardSessionManager
 * @details StandardSessionManager
 * @date 2024-06-11
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2024-06-11               liyuncong          Created
 */
public class StandardSessionManager implements PortalSessionManager, InternalSessionManager {

    private final Object maxActiveUpdateLock = new Object();

    /**
     * The background cleaning thread been started
     */
    private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false);


    // --------------------------------------- InternalSessionManager
    /**
     * The set of currently active Sessions for this Manager, keyed by session identifier
     */
    protected Map<String, InternalSession> sessions = new ConcurrentHashMap<>();

    /**
     * The maximum number of active Sessions allowed, or -1 for no limit
     */
    protected int maxActiveSessions = -1;

    /**
     * Number of session creations that failed due to maxActiveSessions
     */
    protected int rejectedSessions = 0;

    /**
     * The default maximum inactive interval for Sessions created by this Manager
     */
    protected int maxInactiveInterval = 30 * 60;

    /**
     * The internalSession max active number when many thread to add session
     */
    protected volatile int maxActive = 0;

    /**
     * Processing time during session expiration
     */
    protected long processingTime = 0;

    /**
     * Frequency of the session expiration, and related manager operations.
     * Manager operations will be done once for the specified amount of backgroundProcess calls
     */
    protected int processExpiresFrequency = 6;

    /**
     * background processor delay in seconds
     */
    protected int backgroundProcessorDelay = 10;

    /**
     * Iteration count for background processing
     */
    private int count = 0;

    @Override
    public PortalSession getSession(String sessionId) {
        return getSession(sessionId, true);
    }

    @Override
    public PortalSession getSession(String sessionId, boolean create) {
        if (sessionId == null) {
            throw new IllegalStateException("getSession: Session id cannot be null");
        }

        InternalSession session = findSession(sessionId);
        if ((session != null) && !session.isValid()) {
            session = null;
        }
        if (session != null) {
            session.access();
            return session.getSession();
        }

        // Create a new session if requested and the response is not committed
        if (!create) {
            return null;
        }

        session = createSession(sessionId);

        if (session == null) {
            return null;
        }

        session.access();
        return session.getSession();
    }

    @Override
    public InternalSession findSession(String id) {
        if (id == null) {
            return null;
        }
        return this.sessions.get(id);
    }

    @Override
    public InternalSession createSession(String sessionId) {
        if (sessionId == null) {
            throw new IllegalStateException("createSession: Session id cannot be null");
        }

        if ((this.maxActiveSessions >= 0) && (getActiveSessions() >= this.maxActiveSessions)) {
            this.rejectedSessions++;
            throw new TooManyActiveSessionsException("createSession: Too many active sessions", this.maxActiveSessions);
        }

        // Recycle or create a Session instance
        InternalSession session = createEmptySession();

        // Initialize the properties of the new session and return it
        session.setValid(true);
        session.setCreationTime(Instant.now().toEpochMilli());
        session.setMaxInactiveInterval(this.maxInactiveInterval);
        session.setId(sessionId);

        return session;
    }

    @Override
    public void remove(InternalSession session) {
        remove(session, false);
    }

    @Override
    public void remove(InternalSession session, boolean update) {
        if (session.getIdInternal() != null) {
            this.sessions.remove(session.getIdInternal());
        }
    }

    @Override
    public void add(InternalSession session) {
        // When fist session created, start the backgroundProcessThread
        if (!this.backgroundProcessStarted.getAndSet(true)) {
            // do processor delay in seconds
            Timer timer = new Timer("InternalSession-backgroundProcess", true);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    backgroundProcess();
                }
            }, StandardSessionManager.this.backgroundProcessorDelay * 1000L);
        }

        this.sessions.put(session.getIdInternal(), session);
        int size = getActiveSessions();
        if (size > this.maxActive) {
            synchronized (this.maxActiveUpdateLock) {
                if (size > this.maxActive) {
                    this.maxActive = size;
                }
            }
        }
    }

    @Override
    public int getActiveSessions() {
        return this.sessions.size();
    }

    @Override
    public InternalSession createEmptySession() {
        return getNewSession();
    }

    protected InternalSession getNewSession() {
        return new StandardSession(this);
    }

    @Override
    public InternalSession[] findSessions() {
        return this.sessions.values().toArray(new InternalSession[0]);
    }

    @Override
    public void backgroundProcess() {
        this.count = (this.count + 1) % this.processExpiresFrequency;
        if (this.count == 0) {
            processExpires();
        }
    }

    /**
     * Invalidate all sessions that have expired
     */
    public void processExpires() {
        long timeNow = Instant.now().toEpochMilli();
        InternalSession[] internalSessions = findSessions();
        int expireHere = 0;

        for (InternalSession session : internalSessions) {
            if (session != null && !session.isValid()) {
                expireHere++;
            }
        }
        long timeEnd = Instant.now().toEpochMilli();

        this.processingTime += timeEnd - timeNow;
    }

    @Override
    public void setMaxInactiveInterval(int interval) {
        this.maxInactiveInterval = interval;
    }

    @Override
    public void setProcessExpiresFrequency(int processExpiresFrequency) {
        if (processExpiresFrequency <= 0) {
            return;
        }
        this.processExpiresFrequency = processExpiresFrequency;
    }

    @Override
    public void setBackgroundProcessorDelay(int backgroundProcessorDelay) {
        this.backgroundProcessorDelay = backgroundProcessorDelay;
    }

    @Override
    public void setMaxActiveSessions(int max) {
        this.maxActiveSessions = max;
    }
}
